from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response, redirect

import logging


def logout_user(request):
    from django.contrib.auth import logout
    logging.info('logging out user %s' % request.user)
    logout(request)
    return redirect('logoutsuccess')


def handle_configurations(request):
    """Mainly intended to be called with a POST request, where it
    creates a record for the configuration and returns a document with
    information about how to access the completed run (without
    actually doing the run itself; this is tripped off when we try and
    access it).
    """
    # If called via POST, we create a new record:
    if request.method == 'POST':
        logging.info('Received POST request to create a new configuration record')
        from forms import ConnieRunForm, LarvalPhaseFormset
        form = ConnieRunForm(request.POST)

        # Some care needed here; we don't save the form instantly,
        # because we fill in the boundingBox manually; yet to do that
        # we need the total duration and we need the phases for
        # that... hence we save both at the end once all pieces are in
        # place.
        if form.is_valid():
            run = form.save(commit=False)

            formset = LarvalPhaseFormset(request.POST, instance=run)
            if formset.is_valid():
                run.boundingBox = run.calcBounds()
            else:
                # generic exception for now
                return HttpResponseBadRequest(formset.errors)

            # Are they logged in?  If so, hook them up:
            if request.user.is_authenticated():
                logging.debug('User is logged in; associating run record with user %s' % request.user)
                run.user = request.user

            run.save()
            form.save_m2m()
            formset.save_all()
            logging.debug('Saved run record with id %d' % run.id)

            # set up local vars for the locals() hack below:
            final_url = reverse('finalposplot', kwargs={'runid':run.id})
            all_url = reverse('allstatsplot', kwargs={'runid':run.id})
            finaldata_url = reverse('finalstatscsv', kwargs={'runid':run.id})
            alldata_url = reverse('allstatscsv', kwargs={'runid':run.id})
            finalkml_url = reverse('finalstatskml', kwargs={'runid':run.id})
            allkml_url = reverse('allstatskml', kwargs={'runid':run.id})
            logging.info('Will redirect to urls (final/all stats) %(final_url)s / %(all_url)s' % locals())
            minx, miny, maxx, maxy = run.boundingBox.extent
            imgwidth, imgheight = run.imgSize()
            return render_to_response('configuration_response.djhtml', locals())
        else:
            # generic exception for now
            return HttpResponseBadRequest(form.errors)
    elif request.method == 'GET' and request.user.is_authenticated():
        logging.info('Received configurations GET request from user ' + str(request.user))
        from connie2_django.connie2.models import ConnieRun
        configs = ConnieRun.objects.filter(user=request.user).order_by('-creationTime')

        from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
        paginator = Paginator(configs, 25)
        page = request.GET.get('page')
        try:
            runs = paginator.page(page)
        except PageNotAnInteger:
            runs = paginator.page(1)
        except EmptyPage:
            runs = paginator.page(paginator.num_pages)
        return render_to_response('configurations.djhtml', { 'configs' : runs,
                                                             'user': request.user })
    return HttpResponseForbidden('You must be logged in to do that.')

class Errors(object):
    "Quick hack class for reporting errors back up the chain."
    def __init__(self, msg):
        self.num_errors = 1
        self.errors = [(-1, msg)]

def connie_results(runid):
    """Utility method; given a run id either retrieve the cached
    results, or run connie (again) and cache them.  Returns a tuple of
    the run object and the parsed results object."""

    # we need the run object regardless of whether we find the results in the cache
    # or not, because we need the initial polygons in the plot:
    from connie2_django.connie2.models import ConnieRun
    run = get_object_or_404(ConnieRun, pk=runid)

    # we will cache results for a while, using the run id as the key:
    from django.core.cache import cache
    results = cache.get('run%s' % runid)
    if results:
        logging.info('found cached results for id %s; will use those', runid)
    else:
        import connie2_django.connie2.connieparser as parser
        import connie2_django.connie2.connieformatter as formatter
        runFileContents = formatter.formatForConnie(run)
        logging.info('Job id %s Will invoke connie process' % runid)
        logging.debug('Job id %s using input file contents:\n%s' % (runid, runFileContents))

        # run connie..
        try:
            import connie2_django.connie2.interact as interact
            resulttext = interact.invokeConnieProcess(runFileContents)
            logging.info('Job id %s, finished process' % runid)
            logging.debug('Job id %s, received results of:\n%s' % (runid, resulttext))
            results = parser.run_parse(resulttext.splitlines())
            # stick it back in the cache:
            cache.set('run%s' % runid, results)
        except Exception, e:
            err = 'Exception while invoking connie: %s' % e
            logging.error(err)
            results = Errors(err)
    return run, results

def errors_for(runid, errors):
    "Logs the error(s), and returns a suitable response object."
    logging.error('Errors parsing id %s (will abort): %s', runid, errors)
    desc = ('Unfortunately there was an error processing your results; '
            'the following information may help us get to the bottom:\n'
            'Run id: %s\nErrors: %s' % (runid, errors))
    from django.http import HttpResponseServerError
    return HttpResponseServerError(desc)

def perform_run(request, runid, plottype='final'):
    # Idea: check for data{lon,lat}{min,max} parameters in the
    # request.  If we find them, we'll need to re-run (could check
    # that the bounds are different first), so clone the run according
    # to
    # https://docs.djangoproject.com/en/1.4/topics/db/queries/#copying-model-instances
    # and return a redirect.  Same for csv and kml download.
    # Complication: the cache logic is in connie_results, which
    # doesn't have access to the request, but ideally we want to do it
    # before or in that.  I think the compromise is that if we do find
    # those parameters they've already done a run, so they will hit
    # the cache, but it shouldn't impact performance /too/ much.
    if 'datalonmin' in request.GET or \
            'datalatmin' in request.GET or \
            'datalonmax' in request.GET or \
            'datalatmax' in request.GET:
        logging.info('Requested plot job id %s requires; will create new '
                     'configuration and redirect' % runid)
        from connie2_django.connie2.models import ConnieRun
        run = get_object_or_404(ConnieRun, pk=runid)
        xmin,ymin,xmax,ymax = run.boundingBox.extent
        from django.contrib.gis.geos import Polygon
        run.boundingBox = Polygon.from_bbox((
                float(request.GET.get('datalonmin', xmin)),
                float(request.GET.get('datalatmin', ymin)),
                float(request.GET.get('datalonmax', xmax)),
                float(request.GET.get('datalatmax', ymax))))
        years = run.years.all()
        # need to manually copy m2m years, and foreign key
        # larvalPhases (which entails copying larvalphaselevel I
        # think)
        phases = run.larvalPhases.all()
        for phase in phases:
            levels = phase.levels.all()
            for level in levels:
                level.pk = None
                level.save()
            phase.pk = None
            phase.save()
            phase.levels = levels
        run.pk = None           # force creation of a new run object to redirect to:
        run.save()
        run.larvalPhases = phases
        run.years = years
        # Redirect to the new run:
        urlmap = {'final':'finalposplot', 'all':'allstatsplot'}
        return HttpResponseRedirect(reverse(urlmap[plottype],
                                            args=(run.id,)) + '?standalone=true')

    logging.info('Performing job id %s (requested plot-type: %s)' % (runid, plottype))
    cmap_default = 0
    cmap_idx = request.GET.get('cmap', cmap_default)
    try:
        cmap_idx = int(cmap_idx)
        if cmap_idx < 0 or cmap_idx >= len(settings.COLOUR_MAPS):
            cmap_idx = cmap_default
    except:
        cmap_idx = cmap_default

    mrdefault = 100
    max_range = request.GET.get('maxrange', mrdefault)
    try:
        max_range = int(max_range)
        if max_range < 0 or max_range > 100:
            max_range = mrdefault
    except:
        max_range = mrdefault
    logging.debug("will use max range of %s" % max_range)

    standalone = 'standalone' in request.GET

    run, plotdata = connie_results(runid)

    # First, check for errors and possibly abort:
    if plotdata.errors:
        return errors_for(runid, plotdata.errors)

    logging.info('Parsed connie results; will plot now.')
    import connie2_django.connie2.plotter as plotter
    img = plotter.plot_results(plotdata, run, plottype, cmap_idx=cmap_idx, max_range=max_range, standalone=standalone,
                               lonmin=request.GET.get('lonmin',None),
                               latmin=request.GET.get('latmin',None),
                               lonmax=request.GET.get('lonmax',None),
                               latmax=request.GET.get('latmax',None))
    response = HttpResponse(content_type='image/png')
    response.write(img)
    return response

def download_csv_results(request, runid, plottype='final'):
    run, results = connie_results(runid)
    # First, check for errors and possibly abort:
    if results.errors:
        return errors_for(runid, results.errors)

    isfinal = plottype == 'final'
    stats = results.finalStats if isfinal else results.stats

    # write header:
    response = HttpResponse(content_type='text')
    if isfinal:
        response.write('# Final Position Probabilities\n')
    else:
        response.write('# Cumulative Probabilities\n')
    response.write('# Permalink: http://%s%s\n' % (request.get_host(),
                                                   reverse('finalstatscsv' if isfinal else 'allstatscsv',
                                                           kwargs={'runid':runid})))
    response.write('# Cell dimensions %s x %s degrees\n' % (run.velocityFields.cellWidth,
                                                            run.velocityFields.cellHeight))
    response.write('# %s\n' % str(run))
    response.write('# Initial cells: ')
    for cell in run.regions:
        lon,lat,_,_ = cell.extent
        response.write('%s,%s; ' % (lon,lat))
    response.write('\n')
    response.write('# Coordinates refer to bottom-left of the cell:\n')
    response.write('# Longitude, Latitude, Probability\n')

    lonmin = results.lon_min
    latmin = results.lat_min
    lonmax = results.lon_max
    latmax = results.lat_max

    lon_inc = results.lon_inc
    lat_inc = results.lat_inc

    rows, cols = stats.shape

    for c in xrange(cols):
        for r in xrange(rows):
            val = stats[r,c]
            # Only print non-zero results:
            if not val:
                continue
            response.write('%s,%s,%s\n' % (lonmin + lon_inc*c,
                                           latmin + lat_inc*r,
                                           stats[r,c]))

    return response

def download_kml_results(request, runid, plottype='final'):
    # This is quite inefficient -- or at least ugly.  We retrieve the
    # results twice; the cache mechanism should ensure we don't do
    # /too/ much extra work, but ideally we'd refactor this some-how.
    # Basically, we just need the cached results for the plot extent,
    # but we rely on the perform_run function to calculate all
    # offsets, do the plotting, etc.
    run, plotdata = connie_results(runid)
    # First, check for errors and possibly abort:
    if plotdata.errors:
        return errors_for(runid, plotdata.errors)

    img_response = perform_run(request, runid, plottype)

    img_name = 'connie-' + str(runid) + '-' + plottype + '.png'

    # template parameters:
    params = {
        'type': plottype,
        'id': runid,
        'name': img_name,
        'north': plotdata.lat_max,
        'south': plotdata.lat_min,
        'east': plotdata.lon_max,
        'west': plotdata.lon_min
        }

    response = HttpResponse()
    response['Content-Type'] = 'application/vnd.google-earth.kmz'
    response['Content-Disposition'] = 'filename=connie_results-%s-%s.kmz' % (runid, plottype)

    import zipfile
    from cStringIO import StringIO
    from django.template.loader import render_to_string
    buffer = StringIO()
    # Note we can't use the 'with ZipFile...' syntax just yet, because
    # that isn't supported on python2.6 which we still run on the
    # server:
    zip = zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED)
    # First the KML:
    zip.writestr('connie.kml',
                 render_to_string('gearth.kml', params))
    # Then the image:
    zip.writestr(img_name, img_response.content)
    zip.close()

    # clean up and return the buffer contents (which should be a zipfile):
    buffer.flush()
    ret_zip = buffer.getvalue()
    buffer.close()
    response.write(ret_zip)
    return response
